#include "Map.h"
using namespace std;

Map::Map(){
	height = 0;
	width = 0;
	mapFileLocation = "";
	tileSheet = new TileSheet();
	theme="underground";
	finished=false;
	startLoc.first = 96;
	startLoc.second = 512;
}

bool Map::loadMap(string filepath)
{
	cout << "Loading map " << filepath << "\n";
	filepath = "data/maps/" + filepath;

	ifstream mapFile(filepath.c_str(), ifstream::in);

	if(mapFile == NULL)
	{
        return false;
	}
	else
	{
		cout << "ifstream successfull, loading tiles from " << filepath << "\n";
		mapFileLocation = filepath;
		tiles.clear(); //TODO: clear layers of tiles correctly.
		objectLayers.clear();

        string tag = "";
        string str = "";

        int my_type;
		vector<Object*> currentObjects;

        while(getline(mapFile, tag))
        {
            cout << "Tag: \""<<tag<<"\"\n";

            if (tag == "THEME")
            {
                mapFile >> theme;
                if(theme == "ice")
                    friction = 20;
                else
                    friction = 40;
            }
            else if (tag == "SIZE")
            {
                mapFile >> width; //in tiles
                mapFile >> height; //in tiles
            }
            else if (tag == "OBJECT_LAYER")
            {
                string type;
                string layer;
                string orientation;
                Direction orient;

                mapFile >> layer;

                string end = "/" + layer;

                int x, y;
                mapFile >> type;
                do
                {
                mapFile >> x;
                mapFile >> y;
                mapFile >> orientation;
                if(orientation == "UP" || orientation == "up")
                {
                    orient = UP;
                }
                else if(orientation == "DOWN" || orientation == "down")
                {
                    orient = DOWN;
                }
                else if(orientation == "RIGHT" || orientation == "right")
                {
                    orient = RIGHT;
                }
                else if(orientation == "LEFT" || orientation == "left")
                {
                    orient = LEFT;
                }

                currentObjects.push_back(new Object(x, y, type, orient));
                mapFile >> type;
                } while (type != end);

                objectLayers.insert(pair< string, vector<Object*> >(layer, currentObjects));
                currentObjects.clear();
                cout << "Completed loading object layer " << layer << " from .map\n";

            }
            else if(tag == "TERRAIN" )
            {
                for (int y=0; y<height; y++)
                {
                    for(int x=0; x<width; x++)
                    {
                        mapFile >> my_type;
                        if( mapFile.fail() || my_type<-10 || my_type >55 )
                        {
                            mapFile.close();
                        }
                        if(my_type == -1)
                        {
                            startLoc.first = x;
                            startLoc.second = y;
                        }
                        tiles.push_back(new Tile(x,y, my_type)); // add the tile to the map;
                    }
                }
                cout << "Completed loading tiles from .map\n";
            }
            else if (tag == "EXIT")
            {

            }
            else if (tag == "DOOR")
            {

            }
            else if(tag[0] == '#' || tag=="" || tag==" " || tag=="  ")
            {
                //this is a comment
            }
            else
            {
                cout << "\nIn map loading:\n";
                cout << "   Unknown tag: " << tag << "\nMap loading ended early.\n\n";
                mapFile.close();
                return false;
            }
        }
		cout << "Map start Location at (" << startLoc.first << "," << startLoc.second << ")\n";
		mapFile.close();
        cout << "loadMap finished\n\n";
		return true;
	}

	cout << "Loading default.map failed\n\n";
	mapFile.close();
	return false;
}

Map::~Map(){
}

void Map::loadObjectImages(Artist& artist)
{
    if(objectLayers.empty())
        return;

    map<string, vector<Object*> >::key_compare comp;

    string last=objectLayers.rbegin()->first;

    map<string, vector<Object*> >::iterator it = objectLayers.begin();
    do
    {
       cout << "loading " << (*it).first << endl;

        for(int i = 0; i < objectLayers[(*it).first].size(); i++)
        {
            objectLayers[(*it).first][i]->set_image(artist);
        }
    }while(comp((*it++).first, last));
}

pair<int, int> Map::get_start()
{
    return startLoc;
}

void Map::set_start(pair<int, int> start)
{
    startLoc.first = start.first;
    startLoc.second = start.second;
}

vector<Tile*> Map::get_Tiles()
{
	return tiles;
}

Tile* Map::get_Tile(int x, int y)
{
	if(x < 0 || y < 0 || y >= height || x >= width)
			return NULL;
	return tiles[x+y*width];
}

Tile* Map::get_Tilepx(int x, int y)
{
	return get_Tile(x/32, y/32);
}

int Map::get_Width(){
	return width;
}

int Map::get_Height(){
	return height;
}

string Map::get_theme(){
    return theme;
}

int Map::get_fric()
{
    return friction;
}

void Map::set_theme(string th){
    theme=th;
}

int Map::get_Widthpx(){
	return width*32;
}

int Map::get_Heightpx(){
	return height*32;
}

void Map::set_Width(int newWidth)
{
    width = newWidth;
}

void Map::set_Height(int newHeight)
{
    height = newHeight;
}

bool Map::solid_collision(Direction direction, SDL_Rect rect)
{
    solid_collision(direction, rect.x, rect.y, rect.w, rect.h);
}

bool Map::solid_collision(Direction direction, int x, int y, int w, int h){
    if((x< 0) || (x+w > get_Widthpx()) || (y<0) || (y+h >= get_Heightpx()))
    {
    //cout<<"Map boundry collision.\n";
        return true;
    }
    int type, tile_x, tile_y;
    tile_x = (x/32)*32;
    tile_y = (y/32)*32;
/*
    type = get_Tiles()[x/32 + (y/32*get_Width())]->get_type();
    if(tile_test(type, tile_x, tile_y, x, y, w, h))
        return true;
    type = get_Tiles()[x/32+ 1 + (y/32*get_Width())]->get_type();
    if(tile_test(type, tile_x+32, tile_y, x, y, w, h))
        return true;
    type = get_Tiles()[x/32 + (((y)/32+1)*get_Width())]->get_type();
      if(tile_test(type, tile_x, tile_y+32, x, y, w, h))
        return true;
    type = get_Tiles()[x/32 + 1 + ((y/32+1)*get_Width())]->get_type();
      if(tile_test(type, tile_x+32, tile_y+32, x, y, w, h))
        return true;
*/

    //top left collision tile
    if(direction==UP || direction==LEFT || (x+3)%32<6 || (y+3)%32<6){
    type = get_Tiles()[x/32 + (y/32*get_Width())]->get_type();
    //cout << "testing top left collision with     (" << tile_x/32 << "," << tile_y/32 << ") px:(" << tile_x << "," << tile_y << ") type " << type << ": ";
    if(tile_test(type, tile_x, tile_y, x, y, w, h))
        return true;
    }
    //top right collision tile
    if(direction==UP || direction==RIGHT || (x+3)%32<6 || (y+2)%32<6){
    type = get_Tiles()[x/32+ 1 + (y/32*get_Width())]->get_type();
    //cout << "testing top right collision with    (" << tile_x/32 + 1 << "," << tile_y/32 << ") px:(" << tile_x+32 << "," << tile_y << ") type " << type << ": ";
    if(tile_test(type, tile_x+32, tile_y, x, y, w, h))
        return true;
    }
    //bottom left collision tile
    if(direction==DOWN||direction==LEFT || (x+3)%32<6 || (y+3)%32<6){
    type = get_Tiles()[x/32 + (((y)/32+1)*get_Width())]->get_type();
    //cout << "testing bottom left collision with  (" << tile_x/32 << "," << tile_y/32+1 << ") px:(" << tile_x << "," << tile_bottom << ") type " << type << ": ";
    if(tile_test(type, tile_x, tile_y+32, x, y, w, h))
        return true;
    }
    //bottom right collision tile
    if(direction==DOWN||direction==RIGHT || (x+3)%32<6 || (y+3)%32<6){
    type = get_Tiles()[x/32 + 1 + ((y/32+1)*get_Width())]->get_type();
    //cout << "testing bottom right collision with (" << tile_x/32+1 << "," << tile_y/32+1 << ") px:(" << tile_x+32 << "," << tile_y+32 << ") type " << type << ": ";
    if(tile_test(type, tile_x+32, tile_y+32, x, y, w, h))
        return true;
    }

    return false;
}

bool Map::tile_test(int type, int tile_x, int tile_y, SDL_Rect rect)
{
    return tile_test(type, tile_x, tile_y, rect.x, rect.y, rect.w, rect.h);
}

//just a little correction for those freak clipping accidents
void Map::unstick_me(SDL_Rect* box)
{
    int x = box->x;
    int y = box->y;
    int w = box->w;
    int h = box->h;
    if(solid_collision(RIGHT, x, y, w, h) || solid_collision(LEFT, x, y, w, h))
    {
        if(!solid_collision(UP, x, y-1, w, h))
        {
            box->y -= 1;
        }else if(!solid_collision(DOWN, x, y+1, w, h))
        {
            box->y += 1;
        }else if(!solid_collision(RIGHT, x+1, y, w, h))
        {
            box->x += 1;
        }else if(!solid_collision(LEFT, x-1, y, w, h))
        {
            box->x -= 1;
        }
    }
}

bool Map::tile_test(int type, int tile_x, int tile_y, int x, int y, int w, int h){
    int r = x + w ;
    int b = y + h -1;
    int tile_right = tile_x+32;
    int tile_bottom = tile_y+32;

    if(!type)
        return false;
    else if(type==33){
        if((tile_bottom >= y)&&(tile_y+16 <= b )&&(tile_right>=x)&&(tile_x<=r)){
            return true;
        }
    }else if(type==30){
        if((tile_y + 16 >= y)&&(tile_y <= b)&&(tile_right>=x)&&(tile_x<=r)){
            return true;
        }
    }else if(type==31){
        if((tile_bottom >= y)&&(tile_y <= b)&&(tile_right>=x)&&(tile_x+16<=r)){
            return true;
        }
    }else if(type==32){
        if((tile_bottom >= y)&&(tile_y <= b)&&(tile_x+16>=x)&&(tile_x<=r)){
            return true;
        }
    }else if (type==3){
            if(r >= tile_x && x <= tile_right && b >= tile_y+16 && y<=tile_bottom){
                if(r <= tile_right && (b - tile_y - 16)*2 > (tile_right - r))
                    return true;
                else if(r > tile_right)
                    return true;
            }
    }else if (type==4){
            if(r >= tile_x && x <= tile_right && b >= tile_y && y<=tile_bottom){
                if(r <= tile_right && (b-tile_y)*2 > (tile_right - r))
                    return true;
                else if(r > tile_right)
                    return true;
            }
    }else if (type==5){
            if(r >= tile_x && x <= tile_right && b >= tile_y && y<=tile_bottom){
                if(x >= tile_x && (b-tile_y)*2 > (x - tile_x))
                    return true;
                else if(x < tile_x)
                    return true;
            }
    }else if (type==6){
            if(r >= tile_x && x <= tile_right && b >= tile_y+16 && y<=tile_bottom){
                if(x >= tile_x && (b - tile_y - 16)*2 > (x - tile_x))
                    return true;
                else if(x < tile_x)
                    return true;
            }
    }else if (type==11){
            if(r >= tile_x && x <= tile_right && b >= tile_y && y<= tile_bottom){
                if(x >= tile_x &&(tile_right - x) >= (y - tile_y))
                    return true;
                else if(x < tile_x)
                    return true;
            }
    }else if (type==12){
            if(r >= tile_x && x <= tile_right && b >= tile_y && y<= tile_bottom){
                if(r <= tile_right && (r - tile_x) >= (y - tile_y))
                    return true;
                else if(r > tile_right)
                    return true;
            }
    }else if (type==18){
            if(r > tile_x && x < tile_right && b > tile_y && y<tile_bottom){
                if(x >= tile_x && (tile_right - x) >= (tile_bottom - b))
                    return true;
                else if(x < tile_x)
                    return true;
            }
    }else if (type==19){
            if(r > tile_x && x <= tile_right && b >= tile_y && y<=tile_bottom){
                if(r <= tile_right && (r - tile_x) >= (tile_bottom - b))
                    return true;
                else if(r > tile_right)
                    return true;
            }
    }else if (type==24){
            if(r >= tile_x && x <= tile_right && b >= tile_y && y<=tile_bottom-16){
                if(r <= tile_right && (tile_bottom - y - 16)*2 > (tile_right - r))
                    return true;
                else if(r > tile_right)
                    return true;
            }
    }else if (type==25){
            if(r >= tile_x && x <= tile_right && b >= tile_y && y<=tile_bottom){
                if(r <= tile_right && (tile_bottom - y)*2 > (tile_right - r))
                    return true;
                else if(r > tile_right)
                    return true;
            }
    }else if (type==26){
            if(r >= tile_x && x <= tile_right && b >= tile_y && y<=tile_bottom){
                if(x >= tile_x && (tile_bottom - y)*2 > (x - tile_x))
                    return true;
                else if(x < tile_x)
                    return true;
            }
    }else if (type==27){
            if(r >= tile_x && x <= tile_right && b >= tile_y && y<= tile_bottom-16){
                if(x >= tile_x && (tile_bottom - y - 16)*2 > (x - tile_x))
                    return true;
                else if(x < tile_x)
                    return true;
            }
    }else if (type==28){
            if(r >= tile_x+16 && x <= tile_right && b >= tile_y && y<=tile_bottom){
                if(r <= tile_right && (r - tile_x - 16)*2 > (tile_bottom - b))
                    return true;
                else if(r > tile_right)
                    return true;
            }
    }else if (type==29){
            if(r >= tile_x && x <= tile_x+16 && b >= tile_y && y<=tile_bottom){
                if(x >= tile_x && (tile_right - x - 16)*2 > (tile_bottom - b))
                    return true;
                else if(x < tile_x)
                    return true;
            }
    }else if (type==35){
            if(r >= tile_x && x <= tile_right && b >= tile_y && y<=tile_bottom){
                if(r <= tile_x+16 && (r-tile_x)*2 > (tile_bottom-b))
                    return true;
                else if(r > tile_x+16)
                    return true;
            }
    }else if (type==36){
            if(r >= tile_x && x <= tile_right && b >= tile_y && y<=tile_bottom){
                if(x >= tile_x+16 && (tile_right - x)*2 > (tile_bottom-b))
                    return true;
                else if(x < tile_x+16)
                    return true;
            }
    }else if (type==42){
            if(r >= tile_x && x <= tile_x && b >= tile_y && y<=tile_bottom){
                if(r <= tile_x+16 && (r-tile_x)*2 > (y - tile_y))
                    return true;
                else if(x > tile_x+16)
                    return true;
            }
    }else if (type==43){
            if(r >= tile_x && x <= tile_right && b >= tile_y && y<=tile_bottom){
                if(x >= tile_x +16 && (tile_right - x)*2 > (y - tile_y))
                    return true;
                else if(x < tile_x +16)
                    return true;
            }
    }else if (type==49){
            if(r >= tile_x+16 && x <= tile_right && b >= tile_y && y<=tile_bottom){
                if(r >= tile_x +16 && (r - tile_x - 16)*2 > (y - tile_y))
                    return true;
                else if(r < tile_x +16)
                    return true;
            }
    }else if (type==50){
            if(r >= tile_x && x <= tile_x+16 && b >= tile_y && y<= tile_bottom){
                if(x >= tile_x && (tile_right - x - 16)*2 > (y - tile_y))
                    return true;
                else if(x < tile_x)
                    return true;
            }
    }else if (type==52){
        finish_map();
        //cout<<"reached and exit.\n";
        return false;
    }else if((tile_bottom >= y)&&(tile_y<= b)&&(tile_right>=x)&&(tile_x<=r)){
        return true;
    }

    return false;
}

void Map::finish_map()
{
    finished=true;
}

/**
 * Draw the background using the bottom right corner of the tileSheet (2x2 squares across the entire background)
 */
void Map::drawBackground(SDL_Rect* camera, SDL_Surface* screen)
{
		SDL_Rect tileLocation;
		tileLocation.h = 64;
		tileLocation.w = 64;

		for(int i=0; i < camera->w/64 + 1 ; i++)
		{
			for(int k=0; k < camera->h/64 + 2; k++)
			{
                tileLocation.x = i*64 - ((camera->x%(64*3))/3);
				tileLocation.y = k*64 - ((camera->y%(64*3))/3);
				SDL_BlitSurface( tileSheet->get_Surface(), &tileSheet->getBackgroundTile(), screen, &tileLocation);
			}
		}
}

void Map::drawObjects(SDL_Rect* camera, SDL_Surface* screen, string layer, bool lock_position)
{
    if(!objectLayers[layer].size())
    {
        //cout << "Layer \"" << layer << "\" is empty.\n";
        return;
    }

    for(int i = 0; i < objectLayers[layer].size(); i++)
    {
        objectLayers[layer][i]->display(*camera, screen, lock_position);
    }
}

/**
 * Draws the map onto the screen by blitting visible tiles starting from the lowest layer first onto the screen surface.
 */
void Map::drawMap(SDL_Surface* screen, SDL_Rect* camera)
{
	drawBackground(camera, screen);

	drawObjects(camera, screen, "background");

    for(vector<Tile*>::iterator tile = tiles.begin(); tile != tiles.end(); tile++)
    {
            blitTile(screen, camera, *tile);
    }
}

/**
 * decide of the tile should be blitted and if so blit it to the screen.
 */
void Map::blitTile(SDL_Surface* screen, SDL_Rect* camera, Tile* tile)
{
	if(	(tile->get_box()->x*32 >= camera->x-32 				//if the tile is within the left side of the screen
		&& tile->get_box()->x*32 < camera->x+camera->w)				//if the tile is within the right side of the screen
		&& (tile->get_box()->y*32 >= camera->y-32 			//if the tile is within the top of the screen
		&& tile->get_box()->y*32 < camera->y+camera->h))    //if the tile is within the bottom of the screen
    {
		//draw the tile to the screen
		SDL_BlitSurface( tileSheet->get_Surface(), &tileSheet->getTileFromSheet(tile), screen, &getTileScreenCoord(tile,camera));
	}
}

/**
 * Get the screen coordinates of a tile given the tile and the current camera.
 */
SDL_Rect Map::getTileScreenCoord(Tile* tile, SDL_Rect* camera)
{
	SDL_Rect screenCoord;
	screenCoord.x = tile->get_box()->x*32 - camera->x;
	screenCoord.y = tile->get_box()->y*32 - camera->y;
	return screenCoord;
}

void Map::set_TileSheet(Artist* artist, string filename)
{
	tileSheet->loadSheet(artist, filename);
}

bool Map::is_done(int x, int y)
{
    if(    get_Tiles()[x/32 + (y/32*get_Width())]->get_type()==52
        || get_Tiles()[x/32+ 1 + (y/32*get_Width())]->get_type()==52
        || get_Tiles()[x/32 + (((y)/32+1)*get_Width())]->get_type()==52
        || get_Tiles()[x/32 + 1 + ((y/32+1)*get_Width())]->get_type()==52)
            return true;
    return false;
}

std::vector<Tile*>* Map::get_TilesPointer(){
    return &tiles;
}

//used by the editor when creating a blank map
void Map::empty()
{
    tiles.clear();

    for (int y=0; y<height; y++)
    {
        for(int x=0; x<width; x++)
        {
            tiles.push_back(new Tile(x,y, 0));
        }
    }

    cout << "Completed emptying the map";

}

//used by the editor when selecting map objects
std::vector<Object*>* Map::getObjects(string layer)
{
    if(!objectLayers[layer].size())
    {
        //cout << "Layer \"" << layer << "\" is empty.\n";
        return NULL;
    }

    std::vector<Object*>* objectlayer = &objectLayers[layer];
    return objectlayer;
}
